#include <QBuffer>
#include <QtDebug>

#include <auto/autoclose.h>
#include <io/io.h>
#include <io/buffercache.h>

#define BUFF_SIZE 1048576

IO::IO(QObject *parent) : QObject(parent)
{
}

qint64 IO::copy(QString srcName, QIODevice& src, QString dstName, QIODevice& dst, qint64 maxCopy, std::function<void (qint64, qint64)> progress) {
    qDebug() << "IO:copy()# [" << srcName << "] => [" << dstName << "]";
    if (!src.isOpen()) {
        return -1;
    }

    if (src.bytesAvailable() <= 0) {
        src.waitForReadyRead(3000);
        if (src.bytesAvailable() <= 0) {
            qDebug() << "IO:copy()# wait for read timeout.";
            return -1;
        }
    }

    BufferCache& cache = BufferCache::instance();
    char* buff = cache.get(BUFF_SIZE);

    qint64 bytesToCopy = maxCopy > 0 ? qMin(maxCopy, src.size()) : src.size();
    qint64 bytesCopied = 0;
    qint64 nCopy;

    // update progress
    if (progress != nullptr) {
        progress(bytesCopied, bytesToCopy);
    }

    // do copy
    while (bytesCopied < bytesToCopy) {
        nCopy = src.read(buff, BUFF_SIZE);
        if (nCopy < 0) {
            qDebug() << "IO::copy()# read from src failed";
            bytesCopied = -1;
            break;
        }

        if (nCopy != dst.write(buff, nCopy)) {
            qDebug() << "IO::copy()# write to dst failed";
            bytesCopied = -1;
            break;
        }

        bytesCopied += nCopy;

        // update progress
        if (progress != nullptr) {
            progress(bytesCopied, bytesToCopy);
        }
    }

    return bytesCopied;
}

qint64 IO::copy(QString srcName, QByteArray& src, QString dstName, QIODevice& dst, qint64 maxCopy, std::function<void (qint64, qint64)> progress) {
    qDebug() << "IO:copy()# [" << srcName << "] => [" << dstName << "]";

    qint64 bytesToCopy = maxCopy > 0 ? qMin(maxCopy, static_cast<qint64>(src.size())) : src.size();

    // update progress
    if (progress != nullptr) {
        progress(0, bytesToCopy);
    }

    // do copy
    if (bytesToCopy != dst.write(src)) {
        return -1;
    }

    // update progress
    if (progress != nullptr) {
        progress(bytesToCopy, bytesToCopy);
    }

    return bytesToCopy;
}

qint64 IO::copy(QString srcName, QIODevice& src, QString dstName, QByteArray& dst, qint64 maxCopy, std::function<void (qint64, qint64)> progress) {
    qDebug() << "IO:copy()# [" << srcName << "] => [" << dstName << "]";
    if (src.bytesAvailable() <= 0) {
        src.waitForReadyRead(3000);
        if (src.bytesAvailable() <= 0) {
            qDebug() << "IO:copy()# wait for read timeout.";
            return -1;
        }
    }

    qint64 bytesToCopy = maxCopy > 0 ? qMin(maxCopy, src.size()) : src.size();

    // update progress
    if (progress != nullptr) {
        progress(0, bytesToCopy);
    }

    // do copy
    dst.append(src.read(bytesToCopy));

    // update progress
    if (progress != nullptr) {
        progress(bytesToCopy, bytesToCopy);
    }

    return bytesToCopy;
}

qint64 IO::copy(QString srcName, QIODevice& src, QString dstName, char* dstBuff, qint64 dstOffset, qint64 dstLen, qint64& nCopy, std::function<void (qint64, qint64)> progress) {
    qDebug() << "IO:copy()# [" << srcName << "] => [" << dstName << "]";
    if (src.bytesAvailable() <= 0) {
        src.waitForReadyRead(3000);
        if (src.bytesAvailable() <= 0) {
            qDebug() << "IO:copy()# wait for read timeout.";
            return -1;
        }
    }

    qint64 bytesToCopy = qMin(dstLen, src.size());

    // update progress
    if (progress != nullptr) {
        progress(0, bytesToCopy);
    }

    // do copy
    nCopy = src.read(dstBuff + dstOffset, dstLen);

    // update progress
    if (progress != nullptr) {
        progress(bytesToCopy, bytesToCopy);
    }

    return bytesToCopy;
}

qint64 IO::copy(QString srcName, char* srcBuff, qint64 srcOffset, qint64 srcLen, QString dstName, QIODevice& dst, std::function<void (qint64, qint64)> progress) {
    qDebug() << "IO:copy()# [" << srcName << "] => [" << dstName << "]";

    // update progress
    if (progress != nullptr) {
        progress(0, srcLen);
    }

    // do copy
    if (srcLen != dst.write(srcBuff + srcOffset, srcLen)) {
        return -1;
    }

    // update progress
    if (progress != nullptr) {
        progress(srcLen, srcLen);
    }

    return srcLen;
}

qint64 IO::session(QIODevice& device, QIODevice::OpenModeFlag flags, std::function<int (QIODevice& device)> callback) {
    // open device if necessary
    AutoClose inClose(device, flags);
    if (!device.isOpen()) {
        return -1;
    }

    // callback
    return callback(device);
}

qint64 IO::session(QIODevice& in, QIODevice& out, std::function<int (QIODevice& in, QIODevice& out)> callback) {
    // open device if necessary
    AutoClose inClose(in, QIODevice::ReadOnly);
    if (!in.isOpen()) {
        return -1;
    }

    AutoClose outClose(out, QIODevice::WriteOnly);
    if (!out.isOpen()) {
        return -1;
    }

    // callback
    return callback(in, out);
}
